from django.db import models
from django.db.models import base
from django.contrib.auth.models import User

import csv
import re
import datetime
import calendar

PAYPAL_RE = "Group Name.*\(([\S]+)\)"
BYPASS_PAYPAL_FILTER = True

_debug = 0


class Supporter(models.Model):
    MEMBER_TYPE_CHOICES = [
                           ('F','Full'), 
                           ('C','Concessionary'),
                           ('I', 'Individual'),
                           ('U', 'Unknown'),
                           ('D', 'Donee'),
                           ]
    PAYMENT_FREQUENCY_CHOICES = (
                                 ('M','Monthly'), 
                                 ('Y','Yearly'),
                                 ('U','Unknown'),
                                 ('D', 'Donee'),
                                 )
    
    MEMBER_STATUS_CHOICES = [('P', 'Paid up'),
                             ('L', 'Lapsed'), 
                             ('C', 'Closed'), 
                             ('N', 'Never paid'),
                             ('U', 'Unknown'),
                             ('E', 'Pending'),
                             ('F', 'Lapsed long term'),
                             ('D', 'Donee'),
                             ]
    PAYMENT_CHOICES = (('B','BACS'), ('P', 'Paypal'), ('U', 'Unknown'))
    
    member_id = models.IntegerField(primary_key=True)
    authcode = models.IntegerField()
    member_type = models.CharField(maxlength=1, 
                                         choices=MEMBER_TYPE_CHOICES)
    payment_frequency = models.CharField(maxlength=1, 
                                         choices=PAYMENT_FREQUENCY_CHOICES)
    status = models.CharField(maxlength=1, 
                                         choices=MEMBER_STATUS_CHOICES)
    payment_source = models.CharField(maxlength=1, 
                                         choices=PAYMENT_CHOICES)

    payment_id = models.CharField(maxlength=100,
                                  null=True,
                                  blank=True)
    email = models.CharField(maxlength=100,
                            blank=True) 
    
    title = models.CharField(maxlength=100)
    first_name = models.CharField(maxlength=100, core=True)
    surname = models.CharField(maxlength=100, core=True)
    organisation = models.CharField(maxlength=100, blank=True)
    
    address1 = models.CharField(maxlength=100, blank=True)
    address2 = models.CharField(maxlength=100, blank=True)
    address3 = models.CharField(maxlength=100, blank=True)
    town = models.CharField(maxlength=100, blank=True)
    county = models.CharField(maxlength=100, blank=True) 
    postcode = models.CharField(maxlength=100, blank=True)
    country = models.CharField(maxlength=100, blank=True)
    telephone = models.CharField(maxlength=100, blank=True) 
    faxno = models.CharField(maxlength=100, blank=True) 

    heard_about_us = models.TextField(maxlength=500, null=True, blank=True) 
    info1 = models.CharField(maxlength=100, null=True, blank=True) 
    info2 = models.CharField(maxlength=100, null=True, blank=True) 
    info3 = models.CharField(maxlength=100, null=True, blank=True) 
    info4 = models.CharField(maxlength=100, null=True, blank=True) 
    info5 = models.CharField(maxlength=100, null=True, blank=True) 
    project = models.CharField(maxlength=100, null=True, blank=True)
     
    group_email_address = models.CharField(maxlength=100, blank=True, null=True) 
    date_joined = models.DateField(auto_now=False, null=True, blank=True)
    date_closed = models.DateField(auto_now=False, null=True, blank=True)
    reason_closed = models.CharField(maxlength=100, blank=True, null=True) 
    formatted_address = models.TextField(maxlength=500, blank=True, null=True)  
    
    def __unicode__(self):
        return u"%s, %s (%s)" % (self.surname, self.first_name, self.member_id)
    

    def __str__(self):
        return "%s, %s (%s)" % (self.surname, self.first_name, self.member_id)

    class Meta:
        ordering = ['surname', 'first_name']
        
    class Admin:
 
        list_display = ('member_id','surname', 'first_name', 'member_type', 'status')
        list_filter = ('date_joined', 'member_type', 'status', 'payment_source','payment_frequency')
        ordering = ('surname',)
        search_fields = ('surname', 'first_name')
        fields = [
               (None,               {'fields': [('member_id','authcode'),
                                                ('title','first_name','surname'),
                                                ('email', 'organisation'),
                                                ('date_joined', 'date_closed', 'reason_closed'),
                                                
                                                ]}),
             ('Payment information', {'fields': [('member_type', 'status'),
                                                ('payment_source','payment_frequency', 'payment_id' )], 
                                               }),
               ('Address information', {'fields': ['address1',
                                                    'address2',
                                                    'address3',
                                                    'town',
                                                    'county',
                                                    'postcode',
                                                    'country',
                                                    'telephone', 
                                                    'faxno',
                                                    'formatted_address'], 
                                    'classes': ['collapse']}),
            ('Other', {'fields': ['heard_about_us',
                                                    'info1',
                                                    'info2',
                                                    'info3',
                                                    'info4',
                                                    'info5',
                                                    'project'], 
                                    'classes': ['collapse']}),
           ]
        
        
        
    def state_as_of(self, as_of_date):
        """supporter state as of a certain date"""
        
        if self.date_joined > as_of_date:
            return None
        
        if self.date_closed and self.date_closed < as_of_date:
            return 'C'
            
        if self.status == 'D':
            return 'D'
        
        payments = self.payment_set.all()
        
        payments = [p for p in payments if p.date < as_of_date]
        
        if len(payments) == 0:
            return 'N'
        
        if self.payment_frequency == 'Y':
            day_limit = 365
        else:
            day_limit = 31
            
        last_payment = payments[0]
        
        if (as_of_date - last_payment.date).days > day_limit:
            return "L"
        
        return "P"
    
    def update_state(self):
        print "entering update state of", self
        
        if self.status == 'D':
            return 
                
        if self.date_closed is not None:
            if _debug:
                print "supporter has a close date so status is closed"
            self.status = 'C'
            self.save()
            return
        
        payments = self.payment_set.order_by('date')
        if len(payments) == 0:
            if _debug:
                print "supporter has no payments so status is never open"
            self.state = 'N'
            self.date_joined = None
            self.save()
            return 
        else:

            
            
            last_payment = payments[len(payments)-1]
            # we'll assume monthly payment unless proved otherwise
            
            self.payment_source = last_payment.source_file.type
            
            full_sub = 500 
            conc_sub = 250
            
            if last_payment.amount == full_sub:
                # Monthly, Full
                self.payment_frequency = 'M'
                self.member_type = 'F'
            elif last_payment.amount == conc_sub:
                # Monthly, Concessionary
                self.payment_frequency = 'M'
                self.member_type = 'C'
            elif last_payment.amount == full_sub * 12:
                # Yearly, Full 
                self.payment_frequency = 'Y'
                self.member_type = 'F'
            elif last_payment.amount == conc_sub * 12:
                # Yearly, Concessionary
                self.payment_frequency = 'Y'
                self.member_type = 'C'
            else:
                self.member_type = 'I'
                if last_payment.amount > full_sub * 12:
                    self.payment_frequency = 'Y'
                else: 
                    self.payment_frequency = 'M'
        


            # the as of date is the first of the month
            today = datetime.date.today()
            if today.month > 1:
                asof_year = today.year
                asof_month = today.month -1
            else:
                asof_year = today.year -1
                asof_month = 12

            lapsed_asofdate = datetime.date(day=1,
                                            month=today.month,
                                            year=today.year)
            
            month_lapse_time = calendar.monthrange(asof_year, asof_month)[1]
                                            
            # is the supporter lapsed?
            last_payment_delta =  (lapsed_asofdate - last_payment.date).days

            if _debug:
                print "supporter has a last payment delta of", last_payment_delta
                print "supporter payment frequency is", self.payment_frequency
            
            if self.payment_frequency == 'M' and last_payment_delta > month_lapse_time:
                if _debug:
                    print "monthly payer so marking lapsed"
                self.status = 'L'
                if last_payment_delta > 120: 
                    self.status = 'F'

            elif self.payment_frequency == 'M' and last_payment_delta <= month_lapse_time:
                if _debug:
                    print "monthly paid up"
                self.status = 'P'

            elif self.payment_frequency == 'Y' and last_payment_delta > 365:
                if _debug:
                    print "yearly payer so marking lapsed"
                self.status = 'L'
                if last_payment_delta > 485: 
                    self.status = 'F'

            elif self.payment_frequency == 'Y' and last_payment_delta <= 365:
                if _debug:
                    print "yearly paid up"
                self.status = 'P'

            else:
                if _debug: 
                    print "WARNING: not updating supporter state"
            
            self.save()
            
            
            return (self.member_type, self.payment_frequency, self.status)



    @classmethod
    def update_all(cls):
        if _debug:
            print "begging update of all supporters"
        [x.update_state() for x in cls.objects.all()]
        if _debug:
            print "done"


    def last_payment_date(self):
        payments = self.payment_set.all()
        if len(payments) == 0:
            return None
        else:
            return payments[0].date

    def next_payment_date(self):
        lpd = self.last_payment_date()
        if lpd is None:
            return None
        if self.payment_frequency == 'Y':
            return lpd + datetime.timedelta(days=365)

        if self.payment_frequency == 'M':
            return lpd + datetime.timedelta(days=31)

        return None

    def calculate_first_payment_date(self):
        payments = self.payment_set.all()
        if len(payments) != 0:
            first_payment = payments[len(payments)-1]
            self.date_joined = first_payment.date
            self.save()
        
    def get_supporter_id(self):
        return "%s%s" % (self.surname[:6].upper(), self.member_id)

                    
                                                    
            
        
        
class PaymentCsvFile(models.Model):
    PAYMENT_CHOICES = (('B','BACS'), ('P', 'Paypal'),
                       ('Q','Cheque'), ('C', 'Cash'))
    date_uploaded = models.DateTimeField(auto_now=True)
    data = models.FileField(upload_to=".")
    type = models.CharField(maxlength=1, 
                            choices=PAYMENT_CHOICES)
        
    def __unicode__(self):
        return u"%s (%s)" % (self.data, self.date_uploaded)

    def __str__(self):
        return "%s (%s)" % (self.data, self.date_uploaded)
    
    def save(self):
        models.Model.save(self)
        if self.data is None or self.data == '':
            return
        if self.type == 'B': 
            self.process_bacs_payments()
        if self.type =='P':
            self.process_paypal_payments()

    
    def process_paypal_payments(self):
        paypal_csv = csv.reader(open('uploads/' + self.data))
        paypal_columns = paypal_csv.next()
        for row in paypal_csv:
            row = [x.strip('"') for x in row]
            paypal = {}
            for (k,v) in zip(paypal_columns, row):
                paypal[k.lstrip()] = v

            id_match = re.match(PAYPAL_RE, paypal['Item Title'])
            if BYPASS_PAYPAL_FILTER or id_match:
                pmnt = Payment()
                pmnt.raw_data = str(row)
                pmnt.date = parse_bac_date(paypal['Date'])
                pmnt.amount = int(float(paypal['Gross'].replace(',','')) * 100)
                pmnt.customer_ref = paypal['Name']
                
                if id_match:
                    pmnt.payment_ref = id_match.group(1)
                else: # echeque payments have no proper ref 
                    pmnt.payment_ref = paypal['From Email Address']
                    
                pmnt.source_file = self
                pmnt.type = self.type
                models.Model.save(pmnt)

                pmnt.do_reconcile()
                
        Supporter.update_all()

                    
           
    
    def process_bacs_payments(self):

        bac_csv = csv.reader(open('uploads/' + self.data))
        bac_columns = bac_csv.next()
        for row in bac_csv:
            row = [x.strip('"') for x in row]
            bac = {}
            for (k,v) in zip(bac_columns, row):
                bac[k] = v

            if bac['Description'] == 'BACS Credit':
                pmnt = Payment()
                pmnt.raw_data = str(row)
                pmnt.date = parse_bac_date(bac['Trans Date'])
                pmnt.amount = int(float(bac['Credit']) * 100)
                pmnt.customer_ref = bac['Customer Ref']
                pmnt.payment_ref = bac['Bank Ref']
                pmnt.source_file = self
                pmnt.type = self.type
                pmnt.do_reconcile()
        Supporter.update_all()

    
    class Admin:
        pass


def parse_reference(ref):
    """refs look like SURNA1234 where SURN is the start of Surname,
    and 1234 is the supporter id"""
    id_match = re.match('([\D]*)(\d+)', ref)
    if id_match is None:
        return None # we couldn't match the supporter
    else:
        return (id_match.group(1), int(id_match.group(2)))

def parse_bac_date(bac):
    (day,month,year) = map(int, bac.split('/'))
    if year < 2000:
        year += 2000
    return datetime.date(day=day, month=month, year=year)


class Payment(models.Model):
    raw_data = models.CharField(maxlength=500, core=True)
    date = models.DateField(core=True)
    amount = models.IntegerField(core=True)
    supporter = models.ForeignKey(Supporter, 
                                  null=True,
                                  edit_inline=models.TABULAR,
                                  num_in_admin=5,
                                  )
    payment_ref = models.CharField(maxlength=200)
    customer_ref = models.CharField(maxlength=200)
    source_file = models.ForeignKey(PaymentCsvFile)
    reconciled = models.BooleanField(default=False)


    type = models.CharField(maxlength=1, 
                            choices=PaymentCsvFile.PAYMENT_CHOICES)
    def do_reconcile(self):

        ref = self.payment_ref

        # try and parse the payment ref 
        ref = parse_reference(ref)
        try: 
            self.supporter = Supporter.objects.get(member_id=ref[1])
            self.reconciled = True
        except:
            if _debug:
                print __name__, "payment not reconciled by normal id, trying direct lookup on reference"


        # otherwise lookup by customer ref
        if not self.reconciled:
            try:
                self.supporter = Supporter.objects.get(
                    payment_id=self.customer_ref)
                self.reconciled = True
                if _debug:
                    print "payment reconciled by customer ref", self.customer_ref
            except:
                pass
        
        models.Model.save(self)
    
    def pence(self):
        return self.amount % 100
    def pounds(self):
        return self.amount / 100
    
    def display_amount(self):
        return str(self)
    
    def __unicode__(self):
        return u"%s.%s"   % (self.pounds(), self.pence())

    def __str__(self):
        return "%s.%s"   % (self.pounds(), self.pence())

    def save(self):
        if _debug:
            print "saving payment"

        if self.payment_ref == '':
            self.payment_ref = self.customer_ref
    
        if self.supporter is not None:
            self.reconciled = True
            try:

                
                # in this case update supporter and related payments                    
                if self.supporter.payment_id != self.payment_ref:
                    #self.payment_ref = self.customer_ref
                    self.supporter.payment_id = self.customer_ref
                    self.supporter.save()

                    if _debug:
                        print "updating supporter", self.supporter, "to use payment ref ",self.payment_ref
                    

                
                        other_payments = Payment.objects.filter(
                            customer_ref=self.customer_ref)
                        for p in other_payments:
                            if p != self:
                                if _debug:
                                    print "updating payment", p, "to ", self.supporter
                            p.supporter = self.supporter
                            p.reconciled = True
                            #p.payment_ref = self.customer_ref
                            models.Model.save(p)

                self.supporter.update_state()
                
            
            except KeyError, reason:
                print reason

            

        models.Model.save(self)
        
    
    class Meta:
        ordering = ['-date']
    
    class Admin:
        list_display = ('display_amount', 'date',
                        'supporter', 'reconciled',
                        'payment_ref', 'type', 'customer_ref')
        list_filter = ('reconciled','date','type','source_file')
        date_hierarchy = 'date'
        search_fields = ('payment_ref', 'customer_ref')
        
    #class Meta:
    #    get_latest_by = 'date'



class LoadSupporterRun(models.Model):
    max_id = models.IntegerField()

    class Admin: 
        pass


    def __str__(self):
        return str(self.max_id)
